﻿/* Copyright (C) 2012 by John Cronin
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace davserv
{
    abstract class Backend
    {
        public class Response
        {
            public string Status;
        }

        public class PropertyStringResponse : StringResponse
        {
            public string PropertyValue;
        }

        public class StringResponse : Response
        {
            public string Value;
        }

        public class ByteResponse : Response
        {
            public byte[] Value;
            public string ContentType;
            public string Encoding;
        }

        public class BoolResponse : Response
        {
            public bool Value;
            public static implicit operator bool(BoolResponse b)
            {
                if(b.Status == "200")
                    return b.Value;
                throw new Exception("BoolResponse: " + b.Status);
            }
        }

        public class UriListResponse : Response
        {
            public List<uri> Value = new List<uri>();
            public static implicit operator List<uri>(UriListResponse r)
            {
                if(r.Status == "200")
                    return r.Value;
                throw new Exception("UriListResponse: " + r.Status);
            }
        }

        public class PropertyName
        {
            public string Name;
            public string Namespace;

            public string StartString()
            {
                if (Namespace == "DAV:")
                    return "<D:" + Name + ">";
                else
                    return "<" + Name + " xmlns=\"" + Namespace + "\">";
            }

            public string EmptyString()
            {
                if (Namespace == "DAV:")
                    return "<D:" + Name + "/>";
                else
                    return "<" + Name + " xmlns=\"" + Namespace + "\"/>";
            }

            public string EndString()
            {
                if (Namespace == "DAV:")
                    return "</D:" + Name + ">";
                else
                    return "</" + Name + ">";
            }
        }

        public abstract ByteResponse Get(uri uri);
        public abstract Response Put(uri uri, byte[] val, string contenttype, string encoding);
        public abstract Response MkCol(uri uri);
        public abstract Response Delete(uri uri);
        public abstract StringResponse GetProperty(uri uri, PropertyName prop_name);
        public abstract Response SetProperty(uri uri, PropertyName prop_name, string val);
        public abstract Response DeleteProperty(uri uri, PropertyName prop_name);

        public abstract BoolResponse Exists(uri uri);
        public abstract BoolResponse IsCollection(uri uri);
        public abstract UriListResponse GetChildren(uri uri);

        internal uri mount_point;
        public uri MountPoint { get { return mount_point; } }
    }

    class BackendManager
    {
        public class BackendManagerEntry
        {
            public uri Relative_uri;
            public Backend Backend;

            public uri Absolute_uri { get { return Backend.MountPoint.Append(Relative_uri); } }
        }

        public class ConfigurationObjectEntry
        {
            public string ConfigurationObject;
            public uri Relative_uri;
        }

        Dictionary<string, BackendManagerEntry> backends = new Dictionary<string, BackendManagerEntry>();
        Dictionary<string, Dictionary<string, string>> conf_objects = new Dictionary<string, Dictionary<string, string>>();

        Dictionary<string, string> def_conf_objects = new Dictionary<string, string>();
        public Dictionary<string, string> DefaultConfigurationObjects { get { return def_conf_objects; } }

        public BackendManager()
        {
            InitDefConfObjects();
        }

        void InitDefConfObjects()
        {
            def_conf_objects["read_order"] = "allow,deny";
            def_conf_objects["read_allow"] = "all";
            def_conf_objects["read_deny"] = "none";

            def_conf_objects["write_order"] = "allow,deny";
            def_conf_objects["write_allow"] = "127.0.0.1";
            def_conf_objects["write_deny"] = "all";

            def_conf_objects["server_string"] = "davserv/0.1";

            def_conf_objects["autoindexhtml"] = "";
            def_conf_objects["dav"] = null;

            def_conf_objects["auth_method"] = "digest";
            def_conf_objects["auth_source"] = "denyall";
            def_conf_objects["auth_source_options"] = "";
            def_conf_objects["auth_realm"] = "DEFAULT_REALM";
        }

        public void AddBackend(uri uri, Backend backend)
        {
            string p = uri.ToString();
            if (!p.EndsWith("/"))
                p = p + "/";

            if (backends.ContainsKey(p))
                throw new Exception("Cannot add backend - mount point already in use");

            backends.Add(p, new BackendManagerEntry { Relative_uri = uri, Backend = backend });
            backend.mount_point = uri;
        }

        public void AddConfigurationObject(string conf_object_type, uri uri, string o)
        {
            string p = uri.ToString();

            if (!conf_objects.ContainsKey(conf_object_type))
                conf_objects[conf_object_type] = new Dictionary<string, string>();

            //if (conf_objects[conf_object_type].ContainsKey(p))
            //    throw new Exception("Cannot add configuration object \"" + conf_object_type + "\"- already defined for uri \"" + p + "\"");

            conf_objects[conf_object_type][p] = o;
        }

        public ConfigurationObjectEntry GetConfigurationObject(string conf_object_type, uri uri)
        {
            uri orig_uri = uri;

            if (conf_objects.ContainsKey(conf_object_type))
            {
                string p = "";
                do
                {
                    p = uri.ToString();

                    if (conf_objects[conf_object_type].ContainsKey(p))
                    {
                        ConfigurationObjectEntry ret = new ConfigurationObjectEntry();
                        ret.ConfigurationObject = conf_objects[conf_object_type][p];
                        ret.Relative_uri = orig_uri.TrimStart(uri);
                        return ret;
                    }

                    uri = uri.Trim();
                } while (p != "/");
            }

            if (def_conf_objects.ContainsKey(conf_object_type))
                return new ConfigurationObjectEntry { Relative_uri = orig_uri, ConfigurationObject = def_conf_objects[conf_object_type] };
            else
                return new ConfigurationObjectEntry { Relative_uri = orig_uri, ConfigurationObject = null };
        }

        public BackendManagerEntry GetBackend(uri uri)
        {
            BackendManagerEntry ret = new BackendManagerEntry();

            uri orig_uri = uri;
            string p = "";
            do
            {
                p = uri.ToString();
                if (!p.EndsWith("/"))
                    p = p + "/";

                if (backends.ContainsKey(p))
                {
                    ret.Backend = backends[p].Backend;
                    ret.Relative_uri = orig_uri.TrimStart(uri);
                    return ret;
                }

                uri = uri.Trim();
            } while (p != "/");

            return ret;
        }
    }
}
